package org.carabiner.infopanel;

import java.awt.Button;
import java.awt.Canvas;
import java.awt.Checkbox;
import java.awt.Choice;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Label;
import java.awt.Panel;
import java.awt.ScrollPane;
import java.awt.Scrollbar;
import java.awt.TextComponent;
import java.awt.Window;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.AbstractButton;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JWindow;

import org.carabiner.util.CarabinerProperties;

/**
 * Abstract implementation for a Rule that provides tree traversal functionality
 * and default inspection behavior. <p/>
 * 
 * You can use this class by overriding the inspection method for the topmost
 * component type you wish to inspect. In many cases, this will be the default
 * method {@link #inspect(Component)}. In this case, it is important to call
 * <code>super.inspect(Component)</code> to allow the AbstractRule to traverse
 * the rest of the component tree. <p/>
 * 
 * @author Ben Rady (benrady@gmail.com)
 * 
 */
public abstract class AbstractRule implements Rule
{
  // Author's Note:
  //
  // This abstract implementation tries to simulate the Visitor design pattern
  // outlined in the GoF book, applying it to the Component heirarchy in Swing.
  // You will notice a lot of runtime type checking in this class to facilitate
  // this design.
  //
  // This is necessary because in order to fully implement the visitor
  // pattern, we would have to add a visit() method to all of the JDK
  // components, which is not possible. It should be noted, however, that
  // normally RTTI is considered harmful.
  //
  // A better implementation of the visitor pattern would improve the
  // reliability
  // of this class...I'm just not sure how you could do it.

  public AbstractRule()
  {
  }

  public Icon getIcon()
  {
    return new ImageIcon(CarabinerProperties.class
        .getResource("defaultIcon.gif"));
  }

  /**
   * {@inheritDoc}<p/>
   * 
   * Given a Component, this method will call the most appropriate inspection
   * method. Sub-classes should not override this method to add inspection
   * logic. That should be added to {@link #inspectComponent(Component)}, or
   * one of the type specific inspection methods, such as
   * {@link #inspectButton(Button)}
   * 
   * @param component
   *          This should be a Component or (more likely) a decendent of
   *          Component
   * @return Map
   */
  public final List inspect(Component component)
  {
    // Direct Known subclasses of a component are:
    // Button, Canvas, Checkbox, Choice, Container, Label, List, Scrollbar,
    // TextComponent

    if (component instanceof Button)
    {
      return inspectButton((Button) component);
    }

    if (component instanceof Canvas)
    {
      return inspectCanvas((Canvas) component);
    }

    if (component instanceof Checkbox)
    {
      return inspectCheckBox((Checkbox) component);
    }

    if (component instanceof Choice)
    {
      return inspectChoice((Choice) component);
    }

    if (component instanceof Container)
    {
      return inspectContainer((Container) component);
    }

    if (component instanceof Label)
    {
      return inspectLabel((Label) component);
    }

    if (component instanceof java.awt.List)
    {
      return inspectList((java.awt.List) component);
    }

    if (component instanceof Scrollbar)
    {
      return inspectScrollbar((Scrollbar) component);
    }

    if (component instanceof TextComponent)
    {
      return inspectTextComponent((TextComponent) component);
    }

    return inspectComponent((Component) component);
  }

  protected List inspectButton(Button button)
  {
    return inspectComponent(button);
  }

  protected List inspectTextComponent(TextComponent textComponent)
  {
    // TextArea, TextField
    return inspectComponent(textComponent);
  }

  protected List inspectScrollbar(Scrollbar scrollbar)
  {
    return inspectComponent(scrollbar);
  }

  protected List inspectList(java.awt.List list)
  {
    return inspectComponent(list);
  }

  protected List inspectLabel(Label label)
  {
    return inspectComponent(label);
  }

  protected List inspectChoice(Choice choice)
  {
    return inspectComponent(choice);
  }

  protected List inspectCanvas(Canvas canvas)
  {
    return inspectComponent(canvas);
  }

  protected List inspectCheckBox(Checkbox checkBox)
  {
    return inspectComponent(checkBox);
  }

  /**
   * Extensions of this class should add generic component inspection by
   * implementing this method.
   * 
   * @param component
   *          Component
   * @return List
   * @see <a
   *      href="http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Component.html">Component
   *      API</a>
   */
  protected List inspectComponent(Component component)
  {
    return Collections.EMPTY_LIST;
  }

  /**
   * Inspects a Swing Container and all it's children. Called by the
   * AbstractRule when a Component is Container, but not a Window. It is
   * suggested that you call super.inspectContainer() when overriding this
   * method to allow the child components in the window to be inspected.
   * 
   * @param container
   *          Window
   * @return List
   */
  protected List inspectContainer(Container container)
  {
    // Direct Known subclasses of a Container are:
    // BasicSplitPaneDivider, CellRendererPane,
    // DefaultTreeCellEditor.EditorContainer, JComponent, Panel, ScrollPane,
    // Window

    // Do a depth first search of the container
    List warnings = new ArrayList();
    Component[] children = container.getComponents();
    for (int i = 0; i < children.length; i++)
    {
      warnings.addAll(inspect(children[i]));
    }

    if (container instanceof JComponent)
    {
      warnings.addAll(inspectJComponent((JComponent) container));
      return warnings;
    }

    if (container instanceof Panel)
    {
      warnings.addAll(inspectPanel((Panel) container));
      return warnings;
    }

    if (container instanceof ScrollPane)
    {
      warnings.addAll(inspectScrollPane((ScrollPane) container));
      return warnings;
    }

    if (container instanceof Window)
    {
      warnings.addAll(inspectWindow((Window) container));
      return warnings;
    }

    warnings.addAll(inspectComponent(container));
    return warnings;
  }

  protected List inspectScrollPane(ScrollPane scrollPane)
  {
    return inspectComponent(scrollPane);
  }

  protected List inspectPanel(Panel panel)
  {
    return inspectComponent(panel);
  }

  protected List inspectWindow(Window window)
  {
    // Direct Known Subclasses:
    // BasicToolBarUI.DragWindow, Dialog, Frame, JWindow

    if (window instanceof Dialog)
    {
      return inspectDialog((Dialog) window);
    }

    if (window instanceof Frame)
    {
      return inspectFrame((Frame) window);
    }

    if (window instanceof JWindow)
    {
      return inspectJWindow((JWindow) window);
    }

    return inspectComponent(window);
  }

  protected List inspectJWindow(JWindow window)
  {
    return inspectComponent(window);
  }

  protected List inspectFrame(Frame frame)
  {
    if (frame instanceof JFrame)
    {
      return inspectJFrame((JFrame) frame);
    }

    return inspectComponent(frame);
  }

  protected List inspectJFrame(JFrame frame)
  {
    return inspectComponent(frame);
  }

  protected List inspectDialog(Dialog dialog)
  {
    // Direct Known Subclasses:
    // FileDialog, JDialog
    return inspectComponent(dialog);
  }

  /**
   * It is not necessary to inspect the children of the JComponent yourself.
   * AbstractRule will call the appropriate inspection methods for you.
   * 
   * @param jComponent
   *          JComponent
   * @return List
   */
  protected List inspectJComponent(JComponent jComponent)
  {
    // Direct Known Subclasses:
    // AbstractButton, BasicInternalFrameTitlePane, Box, Box.Filler,
    // JColorChooser,
    // JComboBox, JFileChooser, JInternalFrame, JInternalFrame.JDesktopIcon,
    // JLabel, JLayeredPane, JList, JMenuBar, JOptionPane, JPanel, JPopupMenu,
    // JProgressBar, JRootPane, JScrollBar, JScrollPane, JSeparator, JSlider,
    // JSpinner, JSplitPane, JTabbedPane, JTable, JTableHeader, JTextComponent,
    // JToolBar, JToolTip, JTree, JViewport

    if (jComponent instanceof AbstractButton)
    {
      return inspectAbstractButton((AbstractButton) jComponent);
    }

    if (jComponent instanceof JLabel)
    {
      return inspectJLabel((JLabel) jComponent);
    }

    // FIXME
    return inspectComponent(jComponent);
  }

  protected List inspectJLabel(JLabel label)
  {
    return inspectComponent(label);
  }

  protected List inspectAbstractButton(AbstractButton button)
  {
    return inspectComponent(button);
  }

  public boolean equals(Object o)
  {
    if (o instanceof Rule)
    {
      Rule rule = (Rule) o;
      return rule.getName().equals(getName());
    }
    return false;
  }

  public int hashCode()
  {
    return getName().hashCode();
  }

  protected Warning createWarning(Component component)
  {
    return new Warning(component, getName(), getDescription(),
        java.awt.Color.red);
  }
}
